package EP::UI;

use strict;

use EP::Config;

use CGI;

use File::Basename;

use EP::Common::RequestAgent;
use HTTP::Request::Common qw(POST GET);
use HTTP::Headers;

use XML::LibXSLT;
use XML::LibXML;

use EP::Common::DBI;
use EP::Common::DBI::Session;

use EP::Common::Query::QueryBuilder;
use EP::Common::Query::OutputBuilder;
use EP::Common::Query::QueryValidator;

use EP::Session;
use EP::Session::Log;
use EP::Session::Status;

use EP::EPC::Core;
use EP::EPC::Interface;

our $CGI_QUERY = $::cgi_query;

# var: $VERSION
# EP:NG version tag
our $VERSION = "Expression Profiler v1.0";

## declare globals
our (
	   # var: %output
	   # hash used for storing XML output from EPC processing
     %output,
	   # var: %errors
	   # hash used for storing error output from EPC processing
     %errors,
	   # var: $PARSER
	   # the global (should be Application-scope) XML::LibXML parser object
     $PARSER,
	   # var: $XSLT
	   # the global (should be Application-scope) XML::LibXSLT parser object
     $XSLT,
	   # var: $EPC_SRC_DOC
	   # the XML source for the current EPC + targets
     $EPC_SRC_DOC,
	   # var: $QV
	   # the QueryValidator object
     $QV,
	   # var: $EP_QUERY_DOC
	   # the EP XML query document
     $EP_QUERY_DOC,
	   # var: $DDD
	   # the global Dynamic Data Dispatcher object
     $DDD,
	   # var: $SLOG
	   # the Session Log object
     $SLOG,
	   # var: $session
	   # the global Session Data object
     $session
   );

sub
render_ui
    {

    #make stderr and stdout unbuffered
    select(STDERR); $| = 1;
    select(STDOUT); $| = 1;

    open XSLTLOG, ">$EP::Config::h->{HTML_ROOT}/tmp/XSLTLOG.txt" or die "Couldn't open xslt log file for writing: $!"
        if DEBUG > 3;

######################################################## PROCESSING START ########################################################

# initialize session

## var: $session_cookie
## the global session cookie
local our $session_cookie = session_init ();
die "Session failed to initialize!" if not $session_cookie;

# prepare EPC source XML
$EPC_SRC_DOC = get_epc_source_xml();

# prepare EPC query
$EP_QUERY_DOC = get_epc_query_doc();

# HACK!!!
# retrieving targets from target_sequence, and adding whatever was not added by get_epc_source_xml().
foreach my $target ( $EP_QUERY_DOC->findnodes('//component[@active=1]/target_sequence/target')->get_nodelist() ) {
  my $n_epc_id = $target->findvalue('@comp_id');
  add_source_epc_xml ( $n_epc_id, $EPC_SRC_DOC, 0 );
  add_epc_targets_xml ( $n_epc_id, $EPC_SRC_DOC );
}
# END HACK

warn $EP_QUERY_DOC->toString();

## WIP ##
# set up session user config variables
if ( $session->owner ) {
  $session->current_folder       ( $CGI_QUERY->param ( 'current_folder_id'       ) ) if $CGI_QUERY->param ( 'current_folder_id' );
  $session->current_root_dataset ( $CGI_QUERY->param ( 'current_root_dataset_id' ) ) if $CGI_QUERY->param ( 'current_root_dataset_id' );
  $session->current_action       ( $CGI_QUERY->param ( 'current_action_id'       ) ) if $CGI_QUERY->param ( 'current_action_id' );
  $session->current_cluster_selection ( $CGI_QUERY->param ( 'current_cluster_selection' ) );# if $CGI_QUERY->param ( 'current_cluster_selection' );
  $session->current_metadata_selection ( $CGI_QUERY->param ( 'current_metadata_selection' ) || undef );# if $CGI_QUERY->param ( 'current_cluster_selection' );
}

#local our $component_node = $EP_QUERY_DOC->findnodes('//component')->pop();
#if ( $component_node ) {
#  $component_node->setAttribute( "analysis_history_tree_visible", $CGI_QUERY->param ( "analysis_history_tree_visible" ) );
#}


## WIP ##
# set up the Dynamic Data Dispatcher
$DDD = EP::EPC::Interface::DDDispatcher->new ( $EP_QUERY_DOC, $session );

# prepare XSLT stylesheet & transform the source XML

# var: $stylesheet
# the UI stylesheet to use for rendering & processing EPCs
local our $stylesheet = $XSLT->parse_stylesheet_file( "$EP::Config::h->{HTML_ROOT}/static/xsl/$EP::Config::h->{BASIC_XSL}" );

$session->owner->epc_size_state ( $CGI_QUERY->param ( "epc_size_state" ) ) if $session->owner and defined $CGI_QUERY->param ( "epc_size_state" );

# TODO/HACK: Cache EPC 100, section 0, subsection 3 (Expr Data Upload/Data Files/ArrayExpress) HTML - it's big, it's AE data, and it rarely changes... :)
if ( $CGI_QUERY->param ( "render_component_ids" ) == 100 
     and $CGI_QUERY->param ( "active_subsection_id___c_100_s_0" ) == 3
     and not ( $CGI_QUERY->param ( "active_component_id" ) == 100 )
     and -e "$EP::Config::h->{TMP}/cached.epc.100.0.3.html"
     and -M "$EP::Config::h->{TMP}/cached.epc.100.0.3.html" < 1.0 ) {
  warn "Will retrieve EPC 100.0.3 GUI from cache.";
  print $CGI_QUERY->header ( -cookie => $session_cookie, -charset => "UTF-8" );
    $SLOG->dump_log ( "$EP::Config::h->{LOG}/session_log." . $session->id . ".xml" );
    print `cat "$EP::Config::h->{TMP}/cached.epc.100.0.3.html"`; 
} else {
  my $results;
  my $hostname = `hostname`;
  $hostname =~ s/\..*\n//;
  
  eval { $results = $stylesheet->transform ( $EPC_SRC_DOC,
                                             XML::LibXSLT::xpath_to_string ( node_name      => $hostname ),
					     XML::LibXSLT::xpath_to_string ( cgi_href       => "$EP::Config::h->{CGI_HREF}" ),
					     XML::LibXSLT::xpath_to_string ( html_href      => "$EP::Config::h->{HTML_HREF}" ),
					     XML::LibXSLT::xpath_to_string ( core_href      => "$EP::Config::h->{CORE_HREF}" ),
					     XML::LibXSLT::xpath_to_string ( session_id     => $session->id ),
					     XML::LibXSLT::xpath_to_string ( session_duration => "$EP::Config::h->{EXPIRE_SESSION}" ),
					     XML::LibXSLT::xpath_to_string ( epc_menu_uri   => "file://$EP::Config::h->{HTML_ROOT}/static/xml/epc_targets.xml" ),
					     XML::LibXSLT::xpath_to_string ( VERSION        => $VERSION ),
					     XML::LibXSLT::xpath_to_string ( show_context_help => $session->owner ? $session->owner->show_context_help : 1 ),
					     XML::LibXSLT::xpath_to_string ( epc_size_state => $session->owner ? $session->owner->epc_size_state : 0 ),
					     XML::LibXSLT::xpath_to_string ( user_level     => $session->owner ? $session->owner->user_level : 0 ),
					   );
       }; if ( $@ ) {
	 close XSLTLOG if DEBUG > 3;
	 die "ERROR: $@, $!\n";
       }

  # dump the session log into its log file
  $SLOG->dump_log ( "$EP::Config::h->{LOG}/session_log." . $session->id . ".xml" );

  # HTML output
  print STDOUT $CGI_QUERY->header ( -cookie => $session_cookie, -charset => "UTF-8" );
  $stylesheet->output_fh ( $results, \*STDOUT );
  
  if ( $CGI_QUERY->param ( "render_component_ids" ) == 100 and $CGI_QUERY->param ( "active_subsection_id___c_100_s_0" ) == 3 and not ( $CGI_QUERY->param ( "active_component_id" ) == 100 ) ) {
    warn "Caching EPC 100.0.3 GUI...";
    $stylesheet->output_file ( $results, "$EP::Config::h->{TMP}/cached.epc.100.0.3.html" );
  }
}

$session->query_id ( undef );
warn "\nEND\n";
    }

########################################################  PROCESSING END  ########################################################

# Sub: get_epc_query_doc
#   Creates the EPC query XML, either by processing the incoming GET/POST request via the QueryBuilder, or (and this
#   will, hopefully, be the way of the future, e.g. for web services) by taking it directly from a POST'ed XML query
#   file.
sub get_epc_query_doc {
  # special case - EP query comes in the shape of an XML file!
  # (in the future this would be the web services entrypoint)
  if ($CGI_QUERY->param("ep_xml_query")) {
    my $get_xml_cmd = $EP::Config::h->{GET_CMD}." ".$CGI_QUERY->param("ep_xml_query");
    my $ep_xml_query = qx ( $get_xml_cmd );

    $EP_QUERY_DOC = $PARSER->parse_string ( $ep_xml_query );

    my $query_id = $CGI_QUERY->param("query_id"); #$EP_QUERY_DOC->findvalue ( '/ep_query/@query_id' );
    $session->query_id ( $query_id ) if  defined $query_id;
  } else {
    $EP_QUERY_DOC = EP::Common::Query::QueryBuilder::build_query ( $CGI_QUERY, $EPC_SRC_DOC, $QV, $session );
    $EP_QUERY_DOC->toFile ( "$EP::Config::h->{TMP}/ep_query.$$.xml", 2 ) if DEBUG > 1;
  }

  return $EP_QUERY_DOC;
}

# Sub: session_init
#   Called to set up session management
#
# Manages:
#  - 1. creation + setting of session cookie
#  - 2. end + clean up on log out/kill session
#  - 3. creation + set-up of XML, XSLT, SessionLog and QueryValidator objects
#  - 4. registration of XSL extension functions
sub session_init {
  # get session id from the cookie
  my $session_id = $CGI_QUERY->cookie( 'ep_session_id' );

  $SLOG      = new EP::Session::Log;

  # IF any of these is true:
  #  a. Session expired
  #  b. User logging off (kill_session)
  #  c. User submits a direct ep_xml_query
  # THEN: end this session and force a new one.
  $session = retrieve EP::Common::DBI::Session ( $session_id );

  reset_query_to_login ( "Your session had expired and was terminated - please log in to start a new session." )
    if $session and $session->{owner} and $session->expired and not $CGI_QUERY->param ( "ep_xml_query" );

  reset_query_to_login ( "Need to be logged in to access the EP components!" )
    if defined $CGI_QUERY->param ("render_component_ids") and $CGI_QUERY->param ("render_component_ids") != 0 and (not $session or ($session and not $session->owner));

  reset_query_to_login ( "Invalid session id ($session_id)!" )
    if not $session and $session_id and ( not $CGI_QUERY->param ( "ep_xml_query" ) );

  if ( $session and ( $session->expired or $CGI_QUERY->param ( "kill_session" ) or $CGI_QUERY->param ( "ep_xml_query" ) ) ) {
    $session->delete();
    undef $session;
    warn "Need to create session";
  }

  $session = EP::Common::DBI::Session->create ({}) if not defined $session;
  warn "Got session $session";

  $PARSER    = new XML::LibXML;
  $XSLT      = new XML::LibXSLT;
  $QV        = new EP::Common::Query::QueryValidator ( $CGI_QUERY );

  $PARSER->expand_xinclude(1);
  register_XSL_extn_fcns();

#  $session->status ( "" );

  return $CGI_QUERY->cookie ( -name => 'ep_session_id', -value => $session->id );
}

# Sub: reset_query_to_login
#   Empties the query and sets up for fresh login with a log message.
sub reset_query_to_login {
  my ($message) = @_;
  $CGI_QUERY->delete_all();
  $CGI_QUERY->param ("render_component_ids", 0 );

  slog ($message);
  warn $message if DEBUG;
}

# sub: register_XSL_extn_fcns
# Registers XSL extension functions in the _ep_ namespace
sub register_XSL_extn_fcns {
  XML::LibXSLT->register_function ( "urn:ep", "process_component",                 \&process_component            );
  XML::LibXSLT->register_function ( "urn:ep", "component_output",                  \&component_output             );
  XML::LibXSLT->register_function ( "urn:ep", "component_errors",                  \&component_errors             );
  XML::LibXSLT->register_function ( "urn:ep", "dynamic_data",                      \&dynamic_data                 );
  XML::LibXSLT->register_function ( "urn:ep", "get_epc_query",                     \&get_epc_query                );
  XML::LibXSLT->register_function ( "urn:ep", "get_username",                      \&get_current_username         );
  XML::LibXSLT->register_function ( "urn:ep", "get_folder_tree",                   \&get_folder_tree              );
  XML::LibXSLT->register_function ( "urn:ep", "get_current_root_dataset_id",       \&get_current_root_dataset_id  );
  XML::LibXSLT->register_function ( "urn:ep", "get_current_analysis_history",      \&get_current_analysis_history );
  XML::LibXSLT->register_function ( "urn:ep", "get_data_tree",                     \&get_data_tree                );
  XML::LibXSLT->register_function ( "urn:ep", "regexp_test",                       \&regexp_test                  );
  XML::LibXSLT->register_function ( "urn:ep", "get_arrayexpress_experiments",      \&get_arrayexpress_experiments );
  XML::LibXSLT->register_function ( "urn:ep", "convert_url_to_path",               \&convert_url_to_path          );

  XML::LibXSLT->debug_callback ( \&xslog ) if DEBUG > 3;
}

# sub convert_url_to_path
# xslt extension function for converting url to actual paths
sub convert_url_to_path {
    my $url = shift;

    my $path = $url;
    
    # check if we deal with /tmp data
    my $tmp_url_base = "$EP::Config::h->{HTML_HREF}/tmp";
    $tmp_url_base =~ s/\//\\\//g;

    if ($url =~ /^$tmp_url_base/) {
        my $tmp_path_base = "$EP::Config::h->{HTML_ROOT}/tmp";

        $path =~ s/$tmp_url_base/$tmp_path_base/;
        return $path;
    }

    my $data_url_base = $EP::Config::h->{EPNG_DATA_URL};
    $data_url_base =~ s/\//\\\//g;

    if ($url =~ /^$data_url_base/) {
        my $data_path_base = $EP::Config::h->{EPNG_DATA_PATH};

        $path =~ s/$data_url_base/$data_path_base/;
        return $path;
    }

    return $url;
}

# sub: regexp_test
# xslt extension function for the regular expression tests
#
# params:
# - string - string to test on
# - regexp - the regular expression to test with
#
# returns:
# result of perl regexp match
sub regexp_test {
  my ( $string, $regexp ) = @_;
  return $string =~ /$regexp/;
}

# sub: get_data_tree
# xslt extension function to return the complete data tree for the logged in user. produces XML containing all folders, datasets and actions for the current user.
sub get_data_tree {
  my $data_tree_xml_doc = $DDD->get_data_tree();

  return $data_tree_xml_doc->findnodes('/');
}

# sub: get_arrayexpress_experiments
# xslt extension function to return arrayexpress experiments.
#
# Superceded by <ep_get_arrayexpress_experiments>.
sub get_arrayexpress_experiments {
  my $ae_expts_xml = $DDD->get_arrayexpress_experiments();

  return $ae_expts_xml->findnodes('/');
}

# sub: get_folder_tree
# xslt extension function to return an XML representation of the folder tree of the logged in user
sub get_folder_tree {
    my $folder_tree_xml_doc = $DDD->get_folder_tree ();

    return $folder_tree_xml_doc->findnodes('/') if $folder_tree_xml_doc;
}

# sub: get_current_analysis_history
# xslt extension function to return an XML representation of the analysis history of the current dataset
#
# Superceded by <ep_change_current_root_dataset>.
sub get_current_analysis_history {
  my $analysis_history_xml_doc = $DDD->get_current_analysis_history_tree ();

  return $analysis_history_xml_doc->findnodes('/') if $analysis_history_xml_doc;
}

# sub: get_current_root_dataset_id
# xslt extension function to return the current root dataset ID
sub get_current_root_dataset_id {
  return $session->current_root_dataset->id if $session->current_root_dataset;
}

# sub: xslog
# prints passed strings to the XSLT LOG. Can be used for debugging XSLT transformations.
#
# See line "XML::LibXSLT->debug_callback ( \&xslog ) if DEBUG > 3" above.
sub xslog {
  print XSLTLOG "[XSLOG] @_\n";
}

# sub: get_current_username
# xslt extension function to return the current user's username
sub get_current_username {
  return $session->owner->username if $session->owner;
}

# Sub: get_epc_query
#   Returns the EPC query document to the XSLT processor.
sub get_epc_query {
  return $EP_QUERY_DOC->findnodes('/*');
}

# Sub: component_output
#   Returns the component output XML to the XSLT processor.
sub component_output
    {
    my ($comp_id) = @_;
    warn "component_output($comp_id) called.\n" if DEBUG > 1;

    return if (not exists ($output{$comp_id})) or ($output{$comp_id} eq "");

    return $output{$comp_id} if $comp_id == -1;

    my $output_str = $output{$comp_id};
    my $output_doc = $PARSER->parse_string($output_str);

    return $output_doc->findnodes('/*');
    }

# Sub: component_errors
#   Returns the component errors XML to the XSLT processor.
sub component_errors
    {
    my ($comp_id) = @_;
    warn "component_errors($comp_id) called.\n" if DEBUG > 1;

    return if (not exists ($errors{$comp_id})) or ($errors{$comp_id} eq "");

    my $errors_str = $errors{$comp_id};
    my $errors_doc = $PARSER->parse_string($errors_str);
    warn "component_errors($comp_id): " . $errors_doc->toString() . "\n" if DEBUG > 1;

    return $errors_doc->getDocumentElement()->findnodes('/*');
    }

# sub: dynamic_data
# xslt extension function to execute a dynamic call vai the dynamic data dispatcher
#
# parameters:
# - comp_id - component id
# - sub_name - dynamic subroutine name to execute
# - @arg - arguments to pass to the subroutine *sub_name*
sub dynamic_data {
  my $comp_id = shift;
  my $sub_name = shift;
  my @arg = @_;

  @arg = () if not @arg;

  warn "[DYNAMIC_DATA] EPC accessed: $comp_id, $sub_name, @arg\n" if DEBUG > 1;
  return $DDD->dynamic_call ( $comp_id, $sub_name, @arg );
}

# sub: create_epc_request
# creates an HTTP POST request out of the EP query doc and other parameters
sub create_epc_request {
  my ($EP_QUERY_DOC, $comp_id, $url, $comp_input_doc) = @_;

  my $FORM_DATA = create_request_form_data ( $EP_QUERY_DOC, $comp_id, $comp_input_doc );
  
  # transform hash reference to array
  my @form_data;
  foreach my $submit_name ( keys %$FORM_DATA ) {
    if ( ref ( $FORM_DATA->{$submit_name} ) eq "ARRAY" ) {
      my @submit = @{$FORM_DATA->{$submit_name}};
      if ( scalar @submit > 0 ) {
	if ( defined $submit[0] and -f $submit[0] ) {
	  push @form_data, $submit_name, \@submit;
	} else {
	  push @form_data, $submit_name, $_ foreach @submit;
	}
      }
    } else {
      push @form_data, $submit_name, $FORM_DATA->{$submit_name};
    }
  }

  my $req = POST( $url, Content_type => "form-data", timeout => 1200, Content => \@form_data );

  # clean up the submitted files from CGI's tmp
  my @files = grep { ref ( $_ ) eq "ARRAY" && -f $$_[0] } @form_data;
  unlink $$_[0] foreach @files;

  return $req;
}

# sub: create_request_form_data
# creates the POST portion of the EPC request out of the EP_QUERY_DOC
sub create_request_form_data {
  my ($EP_QUERY_DOC, $comp_id, $comp_input_doc) = @_;

  # start creating the POST request
  my %FORM_DATA;

  foreach ( $EP_QUERY_DOC->findnodes ( '//component[@id='.$comp_id.']/section' ) ) {
    my $section = $_;
    my $active_subsection_nodes;

    if ( $section->findvalue ( '@submit_all_subsections' ) ) {
      warn "[EPC Create Request] processing all subsections! (EPC $comp_id).\n";
      foreach ( $section->find ( 'subsection' )->get_nodelist() ) {
	process_submits ( $_, \%FORM_DATA, $comp_input_doc );
      }
    } else {
      $active_subsection_nodes = $section->find ( 'subsection[@id=parent::section/@active_subsection_id]' );

      if ( $active_subsection_nodes->size() == 1 ) {
	my $active_subsection = $active_subsection_nodes->pop();
	process_submits ( $active_subsection, \%FORM_DATA, $comp_input_doc );
      }
    }

    process_submits ( $section, \%FORM_DATA, $comp_input_doc );
  }

  # quick hack to get the session id... needed for chaining
  if ( ! $FORM_DATA{"session_id"} ) {
    $FORM_DATA{"session_id"} = $session->id;
  }

  $FORM_DATA{process_component_id} = $comp_id;
  $FORM_DATA{request_user_agent}   = $CGI_QUERY->user_agent();

  # HACK (-ish) here... need to find (finally...) a good way to connect up the components
  $FORM_DATA{current_folder_id}       = $session->current_folder->id if $session->current_folder;
  $FORM_DATA{current_root_dataset_id} = $session->current_root_dataset->id if $session->current_root_dataset;
  $FORM_DATA{current_action_id}       = $session->current_action->id if $session->current_action;
  $FORM_DATA{current_cluster_selection} = $session->current_cluster_selection if $session->current_cluster_selection;
  $FORM_DATA{current_metadata_selection} = $session->current_metadata_selection if $session->current_metadata_selection;

  $FORM_DATA{dst_dataset_id}       = $comp_input_doc->findvalue ( '/epc_output/output[@name="dst_dataset_id"]' ) if $comp_input_doc;
  $FORM_DATA{current_action_id}    = $comp_input_doc->findvalue ( '/epc_output/output[@name="current_action_id"]' ) if $comp_input_doc;

  my @pnames = $CGI_QUERY->param;
  map { $FORM_DATA{$_} = $CGI_QUERY->param($_) if /^chk_/ } @pnames;

return \%FORM_DATA;
}

# sub: process_component
# xslt extension function responsible for EPC processing
#
# performs the following:
# - chooses whether to run EPC locally or remotely (all components are, for now, running remotely)
# - executes a local or remote action for the component
# - checks for success, and if so, transforms the component output
# - returns status (OK or otherwise)
sub process_component {
  my ($comp_id, $comp_input) = @_;

  my $root = $EPC_SRC_DOC->getDocumentElement();
  my $action_nodes = $root->find ( '//component[@id='.$comp_id.']/action' );

  return "Component $comp_id has no action defined!" if $action_nodes->size() == 0;

  ## prepare the "component action" based on the component def'n
  my $action = $action_nodes->get_node(1);
  my $target_id = $action->findvalue ( 'target[1]/@comp_id' ); # HACK? takes only the first target id

  my $res;
  my $url = $EP::Config::h->{EPCLUST_WWW_URL};
  warn "We have $session";
  # $session->status ( "Processing " . $root->findvalue ( '//component[@id='.$comp_id.']/@title' ) . " ... " );
  my $comp_input_doc = $PARSER->parse_string ( $output{$comp_input} ) if $output{$comp_input};
  
  if ( $action->findvalue('method') eq "local" ) {
    $res = local_action ( $action, $comp_id, $comp_input_doc );
  } else {
    ($res, $url) = remote_action ( $action, $comp_id, $comp_input_doc );
  }

  # Check the outcome of the response
  if ($res->is_success) {
    my $err_count = 0;

    my $transform_output = $action->findvalue('target/@transform_xsl');

    $err_count = epc_output ( $res, $action, $comp_id )                  if ( $target_id ne "-1" );
    $err_count = transformed_output ( $res, $action, $transform_output ) if ( $target_id eq "-1" and $transform_output );
    $err_count = basic_container_output ( $res, $url, $comp_id )         if ( $target_id eq "-1" and not $transform_output );

    my $epc_title = $root->findvalue ('//component[@id='.$comp_id.']/@title');
    slog ( "EPC $comp_id, $epc_title, processed" );

    $session->status ( "DONE" );

    return "OK" if $err_count == 0;
    return "Processed with errors!"
  } else {
    $session->status ( "DONE" );
    warn $res->status_line;
    return "HTTP communication error (" . $res->status_line() .")";
  }

  $session->status ( "DONE" );
  return "Failed altogether...";
}

# sub: remote_action
# submits an EPC processing action for remote processing (by EP:NG CORE server)
#
# performs the following:
# - creates request agent
# - creates EPC request
# - sends request
# - receives response
#
#  Returns:
# - response
# - url where the request was sent
sub remote_action {
  my ( $action, $comp_id, $comp_input_doc ) = @_;
  my $url = $action->findvalue ( "url" );

  ## submit the "chunk" of the query that has to do with the component
  ## to the appropriate action. check for success value or error.
  my $ra = EP::Common::RequestAgent->new ( keep_alive => 1, timeout => 1200 );
  $ra->set_credentials ( $action->findvalue ( "auth/username"), $action->findvalue ( "auth/password" ) );
  warn "Going to process $comp_id, $url\n" if DEBUG > 1;
  my $req = create_epc_request ( $EP_QUERY_DOC, $comp_id, $url, $comp_input_doc );

  warn "START PROCESSING REMOTELY...";
  my $res = $ra->request($req);
  warn "FINISH PROCESSING REMOTELY...";
  # in case the EPC changes something in the session

  $session->clear_object_index ();
  $session = retrieve EP::Common::DBI::Session ( $session->id );
  $DDD->{session} = $session;

  open (TEST, ">$EP::Config::h->{HTML_ROOT}/tmp/TESTPOST.txt") or die "Couldn't open for writing: $EP::Config::h->{HTML_ROOT}/tmp/TESTPOST.txt";

  close TEST;

  return ($res, $url);
}

# sub: local_action
# executes component locally
#
# performs the following:
# - creates request form data
# - executes the action [TODO]
# - returns response-like object for output processing
sub local_action {
  my ( $action, $comp_id, $comp_input_doc ) = @_;

  my $request_form_data = create_request_form_data ( $EP_QUERY_DOC, $comp_id, $comp_input_doc );

  $request_form_data->{"local_mode"} = "1";

  my $fake_cgi_query = new CGI( $request_form_data );

  my $core_dd = new EP::EPC::Core::Dispatcher ( $fake_cgi_query );

  $core_dd->dispatch_request() if not $core_dd->{error};

  my $res = $core_dd->result_content();

  $session->clear_object_index ();
  $session = retrieve EP::Common::DBI::Session ( $session->id );
  $DDD->{session} = $session;

  return HTTP::Response->new ( 200, "", HTTP::Headers->new ( Content_Type => 'text/html' ), $res );
}

# sub: epc_output
# formats EPC response into an XML string and stores it for further retrieval into the EPC processing chain by the XSLT stylesheet
sub epc_output {
  my ($res, $action, $comp_id) = @_;

  if ( DEBUG ) {
    open  TEST, ">$EP::Config::h->{HTML_ROOT}/tmp/TESTOUTPUT.html";
    print TEST $res->content;
    close TEST;
  }

  my ($output_doc, $error_doc) = EP::Common::Query::OutputBuilder::build_output ( $action, $res->content );
  my $err_count = $error_doc->getDocumentElement()->findvalue ( 'count(//error)' );

  my $output_xml_filename = "$EP::Config::h->{TMP}/output$$.xml";
  my $error_xml_filename = "$EP::Config::h->{TMP}/error$$.xml";

  if ( $err_count == 0 ) {
    $output{$comp_id} = $output_doc->toString(); #$output_xml_filename if $err_count == 0; # produce output only if there are no errors
    $errors{$comp_id} = "";

    # if we're processing User Management (id 0), may need to update session metadata (just logged in?)
#    EP::Session::set_session_metadata ( \%session ) if not $session{username} and $comp_id == 0;

    warn "[EPC_OUTPUT] Processed and stored output for EPC $comp_id...\n" if DEBUG > 1;
  } else {
    $output{$comp_id} = "";
    $errors{$comp_id} = $error_doc->toString(); # $error_xml_filename;
  }

  slog ( $error_doc, "error" ) if $err_count > 0;
  slog ( $output_doc, "info" ) if $err_count == 0;

  # QUICK hack to get it working with chaining
  if ( ! $CGI_QUERY->param('session_id') ) {
    $CGI_QUERY->param ( -name => "session_id", -value => $session->id );
  }

  return $err_count;
}

# sub: basic_container_output
# takes the EPC response and displays all that was received from EPC procesing within a basic HTML document
sub basic_container_output {
  my ($res, $url, $comp_id) = @_;
  my $r = int(rand(100));

  my $content = $res->content;
  my ($q) = $url =~ /(http:\/\/.+?)\//;
  $content =~ s/<HEAD>/<HEAD><BASE HREF=\"$q\" TARGET=\"_blank\">/;
  $content =~ s/<body/<body onload="top.resize_container_frame ( document );"/;
  $content =~ s/<\/body>/<script>top.resize_container_frame ( document );<\/script><\/body>/;

  my $out_ext = "html";
  $out_ext = "txt" if $res->header("Content-type") eq "text/plain";

  my $out_file = "$EP::Config::h->{TMP}/basic_container_output$$.$r.$out_ext";
  my $out_file_href = $EP::Config::h->{HTML_HREF}."/tmp/basic_container_output$$.$r.$out_ext";

  open ( BASIC_CONTAINER_OUTPUT, ">$out_file") or
    die "Failed to open temporary data file for writing! ($!)";
  print BASIC_CONTAINER_OUTPUT $content;
  close BASIC_CONTAINER_OUTPUT;

  $output{-1} = $out_file_href;

  return 0; # error count returned 0
}

# sub: transformed_output
# takes the EPC response, applies an XSL stylesheet (as indicated in EPC XML) and stores transformed output for display
sub transformed_output {
  my ( $res, $action, $transform_output_xsl ) = @_;

  my $out = $PARSER->parse_string ( $res->content );
  my $action_id = $out->getDocumentElement()->findvalue ( '/epc_output/@action_id' );
  my $output_doc;

  if ( not $action_id ) {
    warn "EPC returned no action to transform!";
    $output_doc = EP::Common::DBI::Action::blank_metadata_xml_doc ( $action );
  } else {
    my $output_action = retrieve EP::Common::DBI::Action ( $action_id + 0 );
    $output_doc = $output_action->metadata_xml_doc ();
  }

  my $err_count = $out->getDocumentElement()->findvalue ( 'count(//error)' );
  my $error_doc = new EP::Common::Query::ErrorBuilder;

  foreach ( @{$out->findnodes ( "/epc_output/error" )} ) {
    $error_doc->getDocumentElement()->appendChild ( $_ );
  }

  if ( $err_count == 0 ) {
    my $transform_xsl = $XSLT->parse_stylesheet_file ( "$EP::Config::h->{HTML_ROOT}/static/xsl/$transform_output_xsl" );

    my @epc_targets;
    foreach ( $action->findnodes ( 'target[@id!=-1]' ) ) {
      my $tgt_epc_id    = $_->findvalue ( '@comp_id' );
      my $tgt_epc_title = $EPC_SRC_DOC->findvalue ( "//component[\@id=$tgt_epc_id]/\@title" );
      push @epc_targets, "$tgt_epc_id,$tgt_epc_title";
    }

    $output{-1} = $transform_xsl->output_string ( $transform_xsl->transform ( $output_doc,
									      XML::LibXSLT::xpath_to_string ( cgi_href    => "$EP::Config::h->{CGI_HREF}" ),
									      XML::LibXSLT::xpath_to_string ( html_href   => "$EP::Config::h->{HTML_HREF}" ),
									      XML::LibXSLT::xpath_to_string ( epc_targets => join ( ":", @epc_targets ) ) ) );
    $errors{-1} = "";
  } else {
    $output{-1} = "";
    my $comp_id = $action->findvalue('ancestor::component/@id');
    warn "Found COMP $comp_id";
    $errors{$comp_id} = $error_doc->toString();
  }

  slog ( $error_doc, "error" ) if $err_count > 0;
#  slog ( $output_doc, "info" ) if $err_count == 0;

  # QUICK hack to get it working with chaining
  $CGI_QUERY->param ( -name => "session_id", -value => $session->id ) if ! $CGI_QUERY->param ( 'session_id' );

  return $err_count;
}

# sub: process_submits
# auxiliary function for EPC POST request preparation
sub process_submits {
  my ( $sec, $form_data, $comp_input_doc ) = @_;

  foreach ( $sec->findnodes ( 'submit' ) ) {
    my $submit = $_;
    ### name___comp-sec-identifier - the identifier needs to be chopped off, hence split
    my $submit_name_ = $submit->findvalue ( '@name' );
    my @submit_name = split /___/, $submit_name_;

    my $the_submit_name = $submit_name[0];

    # in case, due to some weirdness, an input name has ___ in it...
    $the_submit_name = join ( "___", @submit_name[0..($#submit_name-1)] ) if $#submit_name > 1;

    my @submit_value_nodes = ();
    @submit_value_nodes = $comp_input_doc->findnodes ( "/epc_output/output[\@name='$the_submit_name'][\@action_output=1]/value" ) if defined $comp_input_doc;
    @submit_value_nodes = $submit->findnodes ( 'value' ) if not @submit_value_nodes;

    my $is_file = 0;

    my @submit_values;
    foreach ( @submit_value_nodes ) {
      my $submit_value = $_->textContent;

      if ( not "$EP::Config::h->{TMP}/$submit_value" =~ /\n/ ) {
	if (-f "$EP::Config::h->{TMP}/$submit_value" ) {
	  $is_file = 1;
	}
      }
      push @submit_values, $submit_value;
    }

    if ($is_file) {
      $form_data->{$the_submit_name} = ["$EP::Config::h->{TMP}/$submit_values[0]", $submit_values[0], Content_type => "text/plain" ];
    } else {
      $form_data->{$the_submit_name} = \@submit_values;
    }
  }
}

# sub: slog
# writes to session log
sub slog {
  my ($log_what, $severity) = @_;

  $severity = $severity || "info";
  $SLOG->write_log ( $log_what, $severity ) if ref($SLOG);
}

# sub: get_epc_source_xml
# retrieves the EPC source XML by adding the appropriate(*) EPC
# component description files together in one XML.
#
# * - appropriate files are the active component + its targets, which
# are needed to get the proper text in the interface
sub get_epc_source_xml
    {
    my $all_epc_doc = prepare_all_epc_doc();

    my $active_component_id = $CGI_QUERY->param("active_component_id");
    $active_component_id = "" if not defined $active_component_id;

    my  ( $target_component_id
        , $target_owner_component_id
        , $target_id
        ) = split /:/, $CGI_QUERY->param( "targets___c_".$active_component_id ) ? $CGI_QUERY->param( "targets___c_".$active_component_id ) : "::";

    my $render_component_ids = $CGI_QUERY->param ( "render_component_ids" );
    # default render component id = 0 (login) if not logged in or 2 (data selection) if logged in
    if ((not defined $render_component_ids) or $render_component_ids eq "")
        {
        if ( $session->owner )
            {
            $render_component_ids = "2";
            }
        else
            {
            $render_component_ids = "0";
            }
        }

    if ( $active_component_id eq "" )
        {
        add_source_epc_xml  ( $render_component_ids, $all_epc_doc, 0 );
        add_epc_targets_xml ( $render_component_ids, $all_epc_doc );

        $session->{is_epc_active} = 0;
        }
    else {
    warn "[EPC Source Build] Found active component ",
      $CGI_QUERY->param ( "active_component_id" ),
      " with active target $target_component_id\n" if DEBUG > 1;

    $session->{is_epc_active} = 1;

    add_source_epc_xml  ( $active_component_id, $all_epc_doc, 1 );

    # if no target is provided (i.e. active because of some errors and
    # user is just switching tabs), then just pick default targets
    # from its definition.
    if ( $target_component_id eq "" ) {
      add_epc_targets_xml ( $active_component_id, $all_epc_doc );
    } else {
      add_source_epc_xml  ( $target_component_id, $all_epc_doc, 0 );
      add_epc_targets_xml ( $target_component_id, $all_epc_doc );
      add_epc_targets_xml ( $active_component_id, $all_epc_doc );
    }

    # if EPC 20 (Display_Results), will need to retrieve the special value
    # of previous active EPC to get the correct XML sources
    # or 25 (Exp. Design)
    if ( $active_component_id == 20 || $active_component_id == 25 ) {
      add_source_epc_xml ( $CGI_QUERY->param("prev_active_component_id"), $all_epc_doc, 1 );
    }
  }

  if ( DEBUG > 1 ) {
    my $epc_ids = $all_epc_doc->getDocumentElement()->findvalue ('//component/@id');
    warn "[EPC Source Build] Built source doc with EPCs: ", join ( ", ", split(//, $epc_ids)), "\n";
  }

  return $all_epc_doc;
}

# sub: add_epc_targets_xml
# adds targets of given EPC to the EPC doc
sub add_epc_targets_xml {
  my ( $epc_id, $all_epc_doc ) = @_;

  # test for target ids that might need to be added...
  my $epc_target_nodes = $all_epc_doc->getDocumentElement()->find ( '//component[@id='.$epc_id.']/action/target' );
  foreach my $target_node ( $epc_target_nodes->get_nodelist() ) {
    my $epc_target_id = $target_node->findvalue('@comp_id');
    add_source_epc_xml ( $epc_target_id, $all_epc_doc, 1 ); # targets are never displayed
  }
}

# sub: add_source_epc_xml
# adds XML source of given component to the EPC doc
sub add_source_epc_xml {
  my ($comp_id, $all_epc_doc, $no_display) = @_;

  if ( not "" eq $all_epc_doc->getDocumentElement()->findvalue('//component[@id='.$comp_id.']/@id') ) {
    warn "[EPC Source Build] Component $comp_id has already been added!\n" if DEBUG > 1;
    return;
  }

  warn "[EPC Source Build] Adding component $comp_id, source XML: ",  ${ $EP::Config::h->{COMPONENTS} }{$comp_id}, "\n" if DEBUG > 1;
  die "Couldn't find source XML for EPC $comp_id!" if (not -f ${ $EP::Config::h->{COMPONENTS} }{$comp_id} );

  my $comp_doc  = $PARSER->parse_file ( ${ $EP::Config::h->{COMPONENTS} }{$comp_id} );

  if ( $no_display == 1 ) {
    my $top_node = $comp_doc->find ( '/component' )->get_node (1);
    $top_node->setAttribute ( 'display', 0 );
  }

  # cloning the the whole document prior to appending it to $all_epc_doc
  # needed this as simple ressigning caused SegFault in LibXML
  my $comp_node = $comp_doc->getDocumentElement ()->cloneNode (1);
  $all_epc_doc->adoptNode ( $comp_node );
  $all_epc_doc->getDocumentElement ()->appendChild ( $comp_node );
}

# sub: prepare_all_epc_doc
# initial stub of the EPC XML doc
sub prepare_all_epc_doc {
  my $ep_comp_XML = <<__EPCOMPONENTXML__;
<?xml version="1.0"?>
<components/>
__EPCOMPONENTXML__

  my $doc = $PARSER->parse_string ( $ep_comp_XML );
}

1;